#include "OgreStableHeaders.h"

#include "OgreBillboardSet.h"

#include "OgreBillboard.h"
#include "OgreMaterialManager.h"
#include "OgreHardwareBufferManager.h"
#include "OgreCamera.h"
#include "OgreMath.h"
#include "OgreSphere.h"
#include "OgreRoot.h"
#include "OgreRenderSystem.h"
#include "OgreRenderSystemCapabilities.h"
#include "OgreException.h"
#include "OgreStringConverter.h"
#include "OgreLogManager.h"
#include <algorithm>

namespace Ogre
{
	// Init statics
	RadixSort<BillboardSet::ActiveBillboardList, Billboard*, float> BillboardSet::mRadixSorter;

	//-----------------------------------------------------------------------
    BillboardSet::BillboardSet() :
		mBoundingRadius(0.0f), 
        mOriginType( BBO_CENTER ),
        mRotationType( BBR_TEXCOORD ),
        mAllDefaultSize( true ),
        mAutoExtendPool( true ),
        mSortingEnabled(false),
        mAccurateFacing(false),
        mAllDefaultRotation(true),
        mWorldSpace(false),
        mVertexData(0),
        mIndexData(0),
        mCullIndividual( false ),
        mBillboardType(BBT_POINT),
        mCommonDirection(Ogre::Vector3::UNIT_Z),
        mCommonUpVector(Vector3::UNIT_Y),
        mPointRendering(false),
        mBuffersCreated(false),
        mPoolSize(0),
		mExternalData(false)
    {
        setDefaultDimensions( 100, 100 );
        setMaterialName( "BaseWhite" );
        mCastShadows = false;
        setTextureStacksAndSlices( 1, 1 );
    }

	//-----------------------------------------------------------------------
	BillboardSet::BillboardSet(
		const String& name,
		 unsigned int poolSize,
        bool externalData
		) :
		MovableObject(name),
		mBoundingRadius(0.0f), 
        mOriginType( BBO_CENTER ),
        mRotationType( BBR_TEXCOORD ),
        mAllDefaultSize( true ),
        mAutoExtendPool( true ),
        mSortingEnabled(false),
        mAccurateFacing(false),
        mAllDefaultRotation(true),
        mWorldSpace(false),
        mVertexData(0),
        mIndexData(0),
        mCullIndividual( false ),
        mBillboardType(BBT_POINT),
        mCommonDirection(Ogre::Vector3::UNIT_Z),
        mCommonUpVector(Vector3::UNIT_Y),
		mPointRendering(false),
        mBuffersCreated(false),
        mPoolSize(poolSize),
        mExternalData(externalData)
	{
		setDefaultDimensions(100, 100);
		setMaterialName("BaseWhite");
		setPoolSize(poolSize);
		mCastShadows = false;
		setTextureStacksAndSlices(1, 1);
	}

	//-----------------------------------------------------------------------
	BillboardSet::~BillboardSet()
    {
        // Free pool items
        BillboardPool::iterator i;
        for (i = mBillboardPool.begin(); i != mBillboardPool.end(); ++i)
        {
            OGRE_DELETE *i;
        }

        // Delete shared buffers
		_destroyBuffers();
    }
	//-----------------------------------------------------------------------
	Billboard* BillboardSet::createBillboard(
		const Vector3& position,
		const ColourValue& colour)
	{
		if (mFreeBillboards.empty())
		{
			if (mAutoExtendPool)
			{
				setPoolSize(getPoolSize() * 2);
			}
			else
			{
				return 0;
			}
		}

		// Get a new billboard
        Billboard* newBill = mFreeBillboards.front();
		mActiveBillboards.splice(
			mActiveBillboards.end(), mFreeBillboards, mFreeBillboards.begin());
		newBill->setPosition(position);
        newBill->setColour(colour);
        newBill->mDirection = Vector3::ZERO;
        newBill->setRotation(Radian(0));
        newBill->setTexcoordIndex(0);
        newBill->resetDimensions();
        newBill->_notifyOwner(this);

		// Merge into bounds
		Real adjust = std::max(mDefaultWidth, mDefaultHeight);
		Vector3 vecAdjust(adjust, adjust, adjust);
		Vector3 newMin = position - vecAdjust;
		Vector3 newMax = position + vecAdjust;

		mAABB.merge(newMin);
		mAABB.merge(newMax);

		mBoundingRadius = Math::boundingRadiusFromAABB(mAABB);

		return newBill;
	}

	//-----------------------------------------------------------------------
	Billboard* BillboardSet::createBillboard(
		Real x, Real y, Real z, 
		const ColourValue& colour)
	{
		return createBillboard( Vector3( x, y, z ), colour );
	}

	//-----------------------------------------------------------------------
	int BillboardSet::getNumBillboards(void) const
	{
		return static_cast<int>(mActiveBillboards.size());
	}

	//-----------------------------------------------------------------------
	void BillboardSet::clear()
	{
		// Move actives to free list
		mFreeBillboards.splice(mFreeBillboards.end(), mActiveBillboards);
	}

	//-----------------------------------------------------------------------
	Billboard* BillboardSet::getBillboard(unsigned int index) const
	{
		assert(
            index < mActiveBillboards.size() &&
            "Billboard index out of bounds." );

		/* We can't access it directly, so we check wether it's in the first
           or the second half, then we start either from the beginning or the
           end of the list
        */
		ActiveBillboardList::const_iterator it;
		if( index >= ( mActiveBillboards.size() >> 1 ) )
		{
			index = static_cast<unsigned int>(mActiveBillboards.size()) - index;
			for( it = mActiveBillboards.end(); index; --index, --it );
		}
		else
        {
            for( it = mActiveBillboards.begin(); index; --index, ++it );
        }

		return *it;
	}

	//-----------------------------------------------------------------------
	void BillboardSet::removeBillboard(unsigned int index)
	{
		assert(
            index < mActiveBillboards.size() &&
            "Billboard index out of bounds." );

		/* We can't access it directly, so we check wether it's in the first
           or the second half, then we start either from the beginning or the
           end of the list.
           We then remove the billboard form the 'used' list and add it to
           the 'free' list.
        */
        ActiveBillboardList::iterator it;
        if( index >= ( mActiveBillboards.size() >> 1 ) )
        {
			index = static_cast<unsigned int>(mActiveBillboards.size()) - index;
            for( it = mActiveBillboards.end(); index; --index, --it );
        }
        else
        {
            for( it = mActiveBillboards.begin(); index; --index, ++it );
        }

		mFreeBillboards.splice(mFreeBillboards.end(), mActiveBillboards, it);
	}

	//-----------------------------------------------------------------------
	void BillboardSet::removeBillboard( Billboard* pBill )
	{
		ActiveBillboardList::iterator it =
            std::find(mActiveBillboards.begin(), mActiveBillboards.end(), pBill);
		assert(
            it != mActiveBillboards.end() &&
            "Billboard isn't in the active list." );

		mFreeBillboards.splice(mFreeBillboards.end(), mActiveBillboards, it);
	}

	//-----------------------------------------------------------------------
	void BillboardSet::setBillboardOrigin(BillboardOrigin origin)
	{
		mOriginType = origin;
	}

	//-----------------------------------------------------------------------
    BillboardOrigin BillboardSet::getBillboardOrigin(void) const
    {
        return mOriginType;
    }

	//-----------------------------------------------------------------------
    void BillboardSet::setBillboardRotationType(BillboardRotationType rotationType)
    {
        mRotationType = rotationType;
    }
    //-----------------------------------------------------------------------
    BillboardRotationType BillboardSet::getBillboardRotationType(void) const
    {
        return mRotationType;
    }
	//-----------------------------------------------------------------------
	void BillboardSet::setDefaultDimensions( Real width, Real height )
    {
        mDefaultWidth = width;
        mDefaultHeight = height;
    }
	//-----------------------------------------------------------------------
	void BillboardSet::setDefaultWidth(Real width)
	{
		mDefaultWidth = width;
	}
	//-----------------------------------------------------------------------
    Real BillboardSet::getDefaultWidth(void) const
    {
        return mDefaultWidth;
    }
	//-----------------------------------------------------------------------
    void BillboardSet::setDefaultHeight(Real height)
    {
        mDefaultHeight = height;
    }
    //-----------------------------------------------------------------------
    Real BillboardSet::getDefaultHeight(void) const
    {
        return mDefaultHeight;
    }
	//-----------------------------------------------------------------------
	void BillboardSet::setMaterialName(const String& name, const String& groupName /* = ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME */ )
	{
		// early-out
		if (name == mMaterialName)
			return;

		mMaterialName = name;

		mpMaterial = MaterialManager::getSingleton().getByName(name, groupName);

		if (mpMaterial.isNull())
			OGRE_EXCEPT( Exception::ERR_ITEM_NOT_FOUND, "Could not find material " + name,
				"BillboardSet::setMaterialName" );

		/* Ensure that the new material was loaded (will not load again if
           already loaded anyway)
        */
		mpMaterial->load();
	}

	//-----------------------------------------------------------------------
	const String& BillboardSet::getMaterialName(void) const
    {
        return mMaterialName;
    }

	//-----------------------------------------------------------------------
	void BillboardSet::_sortBillboards(Camera* cam)
	{
		switch(_getSortMode())
		{
			case SM_DIRECTION:
				mRadixSorter.sort(mActiveBillboards, SortByDirectionFunctor(-mCamDir));
				break;
			case SM_DISTANCE:
				mRadixSorter.sort(mActiveBillboards, SortByDistanceFunctor(mCamPos));
				break;
		}
	}
	BillboardSet::SortByDirectionFunctor::SortByDirectionFunctor(const Vector3& dir)
        : sortDir(dir)
    {
    }
	float BillboardSet::SortByDirectionFunctor::operator()(Billboard* bill) const
	{
		return sortDir.dotProduct(bill->getPosition());
	}
	BillboardSet::SortByDistanceFunctor::SortByDistanceFunctor(const Vector3& pos)
        : sortPos(pos)
    {
    }
    float BillboardSet::SortByDistanceFunctor::operator()(Billboard* bill) const
    {
        // Sort descending by squared distance
        return - (sortPos - bill->getPosition()).squaredLength();
    }
	//-----------------------------------------------------------------------
	SortMode BillboardSet::_getSortMode(void) const
	{
		// Need to sort by distance if we're using accurate facing, or perpendicular billboard type.
		if (mAccurateFacing ||
            mBillboardType == BBT_PERPENDICULAR_SELF ||
            mBillboardType == BBT_PERPENDICULAR_COMMON)
		{
			return SM_DISTANCE;
		}
		else
		{
			return SM_DIRECTION;
		}
	}
	//-----------------------------------------------------------------------
	void BillboardSet::_notifyCurrentCamera(Camera* cam)
	{
		MovableObject::_notifyCurrentCamera(cam);

		mCurrentCamera = cam;

		// Calculate camera orientation and position
        mCamQ = mCurrentCamera->getDerivedOrientation();
        mCamPos = mCurrentCamera->getDerivedPosition();
		if (!mWorldSpace)
		{
			// Default behaviour is that billboards are in local node space
            // so orientation of camera (in world space) must be reverse-transformed
            // into node space
			mCamQ = mParentNode->_getDerivedOrientation().UnitInverse() * mCamQ;
			mCamPos = mParentNode->_getDerivedOrientation().UnitInverse() *
                (mCamPos - mParentNode->_getDerivedPosition()) / mParentNode->_getDerivedScale();
		}
		// Camera direction points down -Z
		mCamDir = mCamQ * Vector3::NEGATIVE_UNIT_Z;
	}
	//-----------------------------------------------------------------------
	void BillboardSet::beginBillboards(size_t numBillboards)
	{
		/* Generate the vertices for all the billboards relative to the camera
           Also take the opportunity to update the vertex colours
           May as well do it here to save on loops elsewhere
        */

		/* NOTE: most engines generate world coordinates for the billboards
           directly, taking the world axes of the camera as offsets to the
           center points. I take a different approach, reverse-transforming
           the camera world axes into local billboard space.
           Why?
           Well, it's actually more efficient this way, because I only have to
           reverse-transform using the billboardset world matrix (inverse)
           once, from then on it's simple additions (assuming identically
           sized billboards). If I transformed every billboard center by it's
           world transform, that's a matrix multiplication per billboard
           instead.
           I leave the final transform to the render pipeline since that can
           use hardware TnL if it is available.
        */

		// create vertex and index buffers if they haven't already been
		if (!mBuffersCreated)
			_createBuffers();

		// Only calculate vertex offets et al if we're not point rendering
		if (!mPointRendering)
		{
			// Get offsets for origin type
			getParametricOffsets(mLeftOff, mRightOff, mTopOff, mBottomOff);

			// Generate axes etc up-front if not oriented per-billboard
			if (mBillboardType != BBT_ORIENTED_SELF &&
				mBillboardType != BBT_PERPENDICULAR_SELF && 
				!(mAccurateFacing && mBillboardType != BBT_PERPENDICULAR_COMMON))
			{
				genBillboardAxes(&mCamX, &mCamY);

				/* If all billboards are the same size we can precalculate the
				   offsets and just use '+' instead of '*' for each billboard,
				   and it should be faster.
				*/
				genVertOffsets(mLeftOff, mRightOff, mTopOff, mBottomOff,
					mDefaultWidth, mDefaultHeight, mCamX, mCamY, mVOffset);
			}
		}

		// Init num visible
		mNumVisibleBillboards = 0;

		// Lock the buffer
		if (numBillboards) // optimal lock
		{
			// clamp to max
			numBillboards = std::min(mPoolSize, numBillboards);

			size_t billboardSize;
			if (mPointRendering)
			{
				// just one vertex per billboard (this also excludes texcoords)
				billboardSize = mMainBuf->getVertexSize();
			}
			else
			{
				// 4 corners
				billboardSize = mMainBuf->getVertexSize() * 4;
			}
			assert(numBillboards * billboardSize <= mMainBuf->getSizeInBytes());

			mLockPtr = static_cast<float*>(
				mMainBuf->lock(0, numBillboards * billboardSize, 
				HardwareBuffer::HBL_DISCARD) );
		}
		else // lock the entire thing
			mLockPtr = static_cast<float*>(
				mMainBuf->lock(HardwareBuffer::HBL_DISCARD) );
	}
	//-----------------------------------------------------------------------
	void BillboardSet::injectBillboard(const Billboard& bb)
	{
		
	}
}
